﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using ZO.SmartCore.Text;

#endregion

namespace ZO.SmartCore.Protocols.XMPP.SASL.Mechanisms
{
    /// <summary>
    /// Step One Digest-Challenge
    /// </summary>
    public class Step1 : DigestMD5Mechanism
    {

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="Step1"/> class.
        /// </summary>
        public Step1() { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Step1"/> class with specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        public Step1(string message)
        {
            if (String.IsNullOrEmpty(message))
            {
                return;
            }

            Parse(message);
        }

        #endregion

        #region Destructor

        #endregion

        #region Fields
        private string _Realm;
        private string _Nonce;
        private QopOptions _Qop = QopOptions.Auth;
        private CharacterEncoding _Charset = CharacterEncoding.UTF8;
        private string _Algorithm;
        private int _MaxBuf = 65536;
        private bool _Stale;
        private CipherOptions _Cipher;
        private string _RspAuth;

        #endregion

        #region Events

        #endregion

        #region Operators

        #endregion

        #region Properties
        /// <summary>
        /// A list of ciphers that the server supports. This directive must be
        /// present exactly once if "auth-conf" is offered in the
        /// "qop-options" directive, in which case the "3des" and "des" modes
        /// are mandatory-to-implement. The client MUST ignore unrecognized
        /// options; if the client recognizes no option, it should abort the
        /// authentication exchange.
        /// </summary>
        public CipherOptions Cipher
        {
            get { return _Cipher; }
        }

        /// <summary>
        /// A number indicating the size of the largest buffer the server is
        /// able to receive when using "auth-int" or "auth-conf". If this
        /// directive is missing, the default value is 65536. This directive
        /// may appear at most once; if multiple instances are present, the
        /// client should abort the authentication exchange.
        /// </summary>
        public int MaxBuffer
        {
            get { return _MaxBuf; }
        }

        /// <summary>
        /// If the response is valid, the server MAY choose to deem that
        /// authentication has succeeded. However, if it has been too long since
        /// the previous authentication, or for any other reason, the server MAY
        /// send a new "digest-challenge" with a new value for nonce. The
        /// challenge MAY contain a "stale" directive with value "true", which
        /// says that the client may respond to the challenge using the password
        /// it used in the previous response; otherwise, the client must solicit
        /// the password anew from the user
        /// </summary>
        public bool Stale
        {
            get { return _Stale; }
        }

        /// <summary>
        /// Mechanistically, a string which can enable users to know which
        /// username and password to use, in case they might have different
        /// ones for different servers. Conceptually, it is the name of a
        /// collection of accounts that might include the user's account. This
        /// string should contain at least the name of the host performing the
        /// authentication and might additionally indicate the collection of
        /// users who might have access. An example might be
        /// "registered_users@gotham.news.example.com".  This directive is
        /// optional; if not present, the client SHOULD solicit it from the
        /// user or be able to compute a default; a plausible default might be
        /// the realm supplied by the user when they logged in to the client
        /// system. Multiple realm directives are allowed, in which case the
        /// user or client must choose one as the realm for which to supply to
        /// username and password.
        /// </summary>
        public string Realm
        {
            get { return _Realm; }
            protected set { _Realm = value; } // set
        }

        /// <summary>
        /// A server-specified data string which MUST be different each time a
        /// digest-challenge is sent as part of initial authentication.  It is
        /// recommended that this string be base64 or hexadecimal data. Note
        /// that since the string is passed as a quoted string, the
        /// double-quote character is not allowed unless escaped. 
        /// The contents of the nonce are implementation dependent. The
        /// security of the implementation depends on a good choice. It is
        /// RECOMMENDED that it contain at least 64 bits of entropy. The nonce
        /// is opaque to the client. This directive is required and MUST
        /// appear exactly once; if not present, or if multiple instances are
        /// present, the client should abort the authentication exchange.
        /// </summary>
        public string Nonce
        {
            get { return _Nonce; }
            protected set { _Nonce = value; } // set
        }

        /// <summary>
        /// A quoted string of one or more tokens indicating the "quality of
        /// protection" values supported by the server.  The value "auth"
        /// indicates authentication; the value "auth-int" indicates
        /// authentication with integrity protection; the value "auth-conf"
        /// indicates authentication with integrity protection and encryption.
        /// This directive is optional; if not present it defaults to "auth".
        /// The client MUST ignore unrecognized options; if the client
        /// recognizes no option, it should abort the authentication exchange.
        /// </summary>
        public QopOptions Qop
        {
            get { return _Qop; }
            protected set { _Qop = value; } // set
        }

        /// <summary>
        /// This directive, if present, specifies that the server supports
        /// UTF-8 encoding for the username and password. If not present, the
        /// username and password must be encoded in ISO 8859-1 (of which
        /// US-ASCII is a subset). The directive is needed for backwards
        /// compatibility with HTTP Digest, which only supports ISO 8859-1.
        /// This directive may appear at most once; if multiple instances are
        /// present, the client should abort the authentication exchange.
        /// </summary>
        public CharacterEncoding Charset
        {
            get { return _Charset; }
            protected set { _Charset = value; } // set
        }

        /// <summary>
        /// This directive is required for backwards compatibility with HTTP
        /// Digest., which supports other algorithms. . This directive is
        /// required and MUST appear exactly once; if not present, or if
        /// multiple instances are present, the client should abort the
        /// authentication exchange.
        /// </summary>
        public string Algorithm
        {
            get { return _Algorithm; }
            protected set { _Algorithm = value; } // set
        }

        /// <summary>
        /// The server receives and validates the "digest-response". The server
        /// checks that the nonce-count is "00000001". If it supports subsequent
        /// authentication (see section 2.2), it saves the value of the nonce and
        /// the nonce-count. It sends a message formatted as follows:
        /// response-auth = "rspauth" "=" response-value
        /// </summary>
        public string RspAuth
        {
            get { return _RspAuth; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Parse the specified Message
        /// </summary>
        /// <param name="message"></param>
        public void Parse(string message)
        {
            string[] split = message.Split(',');

            foreach (string pair in split)
            {
                string[] splitPair = pair.Split('=');
                string key = splitPair[0];
                string data = splitPair[1];

                if (data.StartsWith("\""))
                {
                    data = data.Substring(1, data.Length - 2);
                }

                switch (key)
                {
                    case "realm":
                        _Realm = data;
                        break;

                    case "nonce":
                        _Nonce = data;
                        break;

                    case "qop":
                        string[] split2 = data.Split('|');

                        foreach (string pair2 in split2)
                        {
                            switch (pair2)
                            {
                                case "auth":
                                    _Qop = _Qop | QopOptions.Auth;
                                    break;

                                case "auth-int":
                                    _Qop = _Qop | QopOptions.AuthInt;
                                    break;

                                case "auth-conf":
                                    _Qop = _Qop | QopOptions.AuthConf;
                                    break;
                            }
                        } // foreach 


                        break;

                    case "cipher":
                        string[] split3 = data.Split('|');

                        foreach (string pair3 in split3)
                        {
                            switch (pair3)
                            {
                                case "des":
                                    _Cipher = _Cipher | CipherOptions.Des;
                                    break;

                                case "3des":
                                    _Cipher = _Cipher | CipherOptions.TripleDes;
                                    break;

                                case "rc4":
                                    _Cipher = _Cipher | CipherOptions.RC4;
                                    break;
                                case "rc4-40":
                                    _Cipher = _Cipher | CipherOptions.RC40;
                                    break;

                                case "rc4-56":
                                    _Cipher = _Cipher | CipherOptions.RC56;
                                    break;
                            }
                        } // foreach 
                        break;

                    case "charset":
                        switch (data)
                        {
                            case "utf-8":
                                _Charset = CharacterEncoding.UTF8;
                                break;

                            case "iso-8859-1":
                                _Charset = CharacterEncoding.ISO88591;
                                break;

                        }
                        break;


                    case "algorithm":
                        _Algorithm = data;
                        break;

                    case "maxbuf":
                        Int32.TryParse(data, out _MaxBuf);
                        break;

                    case "stale":
                        Boolean.TryParse(data, out _Stale);
                        break;

                    case "rspauth":
                        _RspAuth = data;
                        break;
                }
            }
        } // Parse
        #endregion
    }
}
